package com.zmz.IOStream;

import java.io.*;
import java.util.HashMap;

/**
 * Create with IntelliT IDEA
 *
 * @Author: zhengmingzhe
 * @Date: 2022/11/15/20:23
 * @Description: 测试输入输出流。  输入输出都是以内存为概念，比如输入流就是从磁盘读取到内存，输出流就是从内存输出到磁盘，这里有个缓存区概念。
 * <p>
 */
public class IOStream {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
//        outStream();
        //定义录入位置和长度
        partOutsTream();
        //一次读取多个字节
        FileInputStreamTest();
        //一次读取多个字节并转换成字符串
        FileInputStreamTest2();
        //字节输入输出流结合实现文件赋值
        inputAndOutputStream();
        //测试字符输入流
        fileReaderTest();
        //字符输入流并转字符串
        fileReaderTranlateTest();
        //测试字符输出流
        fileWriteTest();
        //测试高效缓冲字节输出流
        bufferOutPutStreamTest();
        //测试高效缓冲字节输入流
        bufferInPutStreamTest();
        //测试字符缓冲输出流
        bufferedWriterTest();
        //测试字符缓冲输入流
        bufferedReaderTest();
        //测试转换流
        transStreamTest();
        //测试序列化
        serializeTest();
    }

    /*
     -----------------字节输出流（OutputStream） 子类 FileOutputStream-----------------
     包含的方法：
     public void close():关闭输出流并释放与此流相关联的任何系统资源。
     public void flush():刷新此输出流并强制任何缓冲的输出字节被写出。
     public void write(byte[] b):将b.length的字节从指定的字节数组写入此输出流
     public void write(byte[] b,int off,int len):从指定的字节数组写入 len 字节，从偏移量 off 开始输出到此输出流
     public abstract void write(int b):将指定的字节写入此输出流。 （一个字节）
     A:close()关闭流对象，但是先刷新一次缓冲区，关闭之后，流对象不可以继续再使用了。
     B:flush()仅仅是刷新缓冲区(一般写字符时要用,因为字符是先进入的缓冲区)，流对象还可以继续使用
     那既然close()就拥有刷新缓冲区的动作，还需要flush()么？
     当然需要，但一般是在1-10M左右刷一次，而不是说每write()一次就flush()一次，那样也会使效率变低。
     **/
    public static void outStream() throws IOException {
        // 1.  public void write(byte[] b):将b.length的字节从指定的字节数组写入此输出流对象（比如写入指定的文件等）
        byte[] bytes = {97, 98, 97, 99, 101, 102};
        FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件.txt");
        //这里会把字节数组写入输出流指定的文件
        fos.write(bytes);
//        fos.flush();
        fos.close();
    }

    /*
     * @Description //定义字节录入位置和长度
     * @Param
     * @return
     **/
    public static void partOutsTream() throws IOException {
        byte[] bytes = {97, 98, 97, 99, 101, 102};
        FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件.txt");
        //定义字节录入位置和长度
        fos.write(bytes, 0, 2);
        fos.close();
    }

    /*
     -------------------------------字节输入流简介----------------------------------
       共性方法：
       int read() 从该输入流读取一个字节的数据。 读取完成后最后会返回一个-1
       int read(byte[] b) 从该输入流读取最多 b.length个字节的数据为字节数组。
       void close() 关闭此文件输入流并释放与流相关联的任何系统资源。
       还有其他的…
     **/
    public static void FileInputStreamTest() throws IOException {
        int i = 0;
        FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
        //判断文件是否为空，若不空，则读取。
        while ((i = fis.read()) != -1) {
            System.out.print(i + ",");
        }
        fis.close();
    }

    /*
     * @Description 一次读取多个字节并转成字符串
     * @Param
     * @return
     **/
    public static void FileInputStreamTest2() throws IOException {
        //定义i为判断返回值是否为-1，即是否取完
        int i = 0;
        FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
        //定义字节数组，大小是2的倍数最好
        byte[] bytes = new byte[1024];
        //调用read方法取字节存储到字节数组中，并将有效字节数返回并赋值给i
        while ((i = fis.read(bytes)) != -1) {
            //将字节数组中的值取出来用String的构造函数转换成字符串，取值范围是0到i（i为有效值）
            System.out.print(new String(bytes, 0, i));
        }
        fis.close();
    }

    /*
     * @Description  输入输出流结合实现文件赋值
     * @Param
     * @return
     **/
    public static void inputAndOutputStream() throws IOException {
        //首先输入到内存，即用输入类
        FileInputStream fis = new FileInputStream("F:\\javaIO流Out类测试文件.txt");
        //其次输出到硬盘，即用输出类
        FileOutputStream fos = new FileOutputStream("F:\\javaIO流Out类测试文件2.txt");
        int i;
        byte[] bytes = new byte[1024];
        //i返回的是有效文件个数
        while ((i = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, i);
        }
        //先关文件输出流
        fos.close();
        fis.close();
        System.out.println("--------");
    }

    /*
------------------------------字符输入流 -----------------------------------
      输入流Reader 子类 FileReader 继承自InputStreamReader，InputStreamReader继承自Reader
      包含的方法：
      int read() 读一个字符
      int read(char[] cbuf) 将字符读入数组。
      abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。
      abstract void close() 关闭流并释放与之相关联的任何系统资源。
     **/
    public static void fileReaderTest() throws IOException {
        FileReader fr = new FileReader("F:\\字符测试文件.txt");
        int len;
        while ((len = fr.read()) != -1) {
            System.out.println((char) len);
        }
        System.out.println("-------------");
    }

    /*
     * @Description  字符输入流 ，转字符串
     * @Param
     * @return
     **/
    public static void fileReaderTranlateTest() throws IOException {
        FileReader fr = new FileReader("F:\\字符测试文件.txt");
        int len;
        //从磁盘输入到字符数组中（内存中）
        char[] chars = new char[1024];
        //用字符数组装字符，并用String类中构造方法将字符转换成字符串
        //String(char[] value, int offset, int count) 分配一个新的 String ，其中包含字符数组参数的子阵列中的字符。
        while ((len = fr.read(chars)) != -1) {
            System.out.println(new String(chars, 0, len));
        }
    }

    /*
      ----------------------------字符输出流 输出流Writer---------------------------
      子类 Writer  FileWriter 继承 OutputStreamWriter 继承 Writer
      包含的方法：
      abstract void close() 关闭流，先刷新。
      abstract void flush() 刷新流。
      void write(char[] cbuf) 写入一个字符数组。
      abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。
      void write(int c) 写一个字符。
      void write(String str) 写一个字符串。
      void write(String str, int off, int len) 写一个字符串的一部分。
     **/
    public static void fileWriteTest() throws IOException {
        FileWriter fw = new FileWriter("F:\\字符测试文件.txt");
        fw.write("知识点");
        fw.flush();//刷新缓冲区数据到文件中
        fw.close();
    }

    /*
     --------------------------------缓冲流（增强流对象===============================）
      字节输出缓冲流（BufferedOutputStream）BufferedOutputStream(OutputStream out) 继承 OutputStream
      构造方法：
      BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流，以将数据写入指定的底层输出流
      BufferedOutputStream(OutputStream out,int size) 创建一个新的缓冲输出流，以将具有指定缓冲区大小的数据写入指定的底层输出流
     **/
    public static void bufferOutPutStreamTest() throws IOException {
        FileOutputStream fos = new FileOutputStream("F:\\缓冲流测试.txt");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write("测试缓冲流".getBytes());
        bos.flush();
        bos.close();
    }

    /*
     ------------------------字节输入缓冲流（BufferedInputStream）------------------------
     继承自InputStream
      构造方法：
      BufferedInputStream(InputStream in) 创建一个BufferedInputStream并保存其参数，输入流 in ，供以后使用。
      BufferedInputStream(InputStream in, int size) 创建BufferedInputStream具有指定缓冲区大小，并保存其参数，输入流 in ，供以后使用。
     **/
    public static void bufferInPutStreamTest() throws IOException {
        FileInputStream fis = new FileInputStream("F:\\缓冲流测试.txt");
        BufferedInputStream bis = new BufferedInputStream(fis);
        //是显示字符串，所以先创建字节数组，将输入的字节存储到字节数组中，然后通过Sting的构造方法将字节数组转换为字符串输出
        int len;
        //从磁盘输入到字节数组中 再转为字符串输出
        byte[] bytes = new byte[1024];
        System.out.println("---------------");
        while ((len = bis.read(bytes)) != -1) {
            System.out.println(new String(bytes));
        }
        bis.close();
    }

    /*
      ---------------------字符缓冲输出流（BufferedWriter）----------------------------
      继承自Write
      构造方法：
      BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
      BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流，使用给定大小的输出缓冲区。
      成员方法(特有)：
      void newLine() 写一行行分隔符。
     **/
    public static void bufferedWriterTest() throws IOException {
        FileWriter fw = new FileWriter("F:\\缓冲流测试文件.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("测试缓冲字符流");
        bw.newLine();
        bw.write("shd");
        bw.flush();
        bw.close();
    }

    /*
      ---------------------字符缓冲输入流（BufferedReader）继承自Reader----------------------
      构造方法：
      BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
      BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流。
      特有的成员方法
      String readLine() 读一行文字。
      以换行符（‘\n’），回传（‘\r’）或者回车后直接跟着换行(\r\n)作为结束行的标志
      如果读取到的这一行无数据了，会返回null值，其他时候返回的是读取的数据（但是不会读取行的终止符合，即如果本来有换行，读取后是不会读取换行符）
     **/
    public static void bufferedReaderTest() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("F:\\缓冲流测试文件.txt"));
        //读的是一行，所以只有一字符串为类型接收数据
        String str;
        while ((str = br.readLine()) != null) {
            System.out.println(str);
        }
        HashMap a = new HashMap();

    }

    /*
     ---------------------------------转换流-------------------------------------------
      作用：在文件进行写入或者读取操作时，文件是以字节形式存储在文件中，然后通过编码表对字节进行解码以呈现看得懂的文字，而就在在编码中，一般情况默
      认使用UTF-8编码，但是有的会使用GBK编码，如果编码录入，与解码读取所用的编码表不同，则会出现乱码情况，此时就可以使用转换流指定使用什么编码表进
      行读取或者写入操作。
      OutputStreamWriter继承自Writer
      InputStreamReader继承自Reader
      构造方法：
      输出的： OutputStreamWriter(OutputStream out)创建一个使用默认字符编码的OutputStreamWriter。 OutputStreamWriter(OutputStream out, String charsetName) 创建一个使用命名字符集的OutputStreamWriter。
      输入的： InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader 。InputStreamReader(InputStream in, String charsetName)创建一个使用命名字符集的InputStreamReader。
      参数：OutputStream，InputStream：分别是字节输出流和字节输入流； charsetName：编码名称，不区分大小写，默认为UTF-8
      使用步骤：
      1.创建OutputStreamWriter对象，构造方法中new一个字节输出流，和指定编码。
      2.用OutputStreamWriter对象，调用方法写入数据，然后刷新缓冲区，释放资源
      3.创建InputStreamReader对象，构造方法new一个字节输入流，和指定编码。
      4.调用方法读取文件，释放资源。
     **/
    public static void transStreamTest() throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("F:\\转换流测试.txt"), "gbk");
        osw.write("转换流测试");
        osw.flush();
        osw.close();
        InputStreamReader isr = new InputStreamReader(new FileInputStream("F:\\转换流测试.txt"), "gbk");
        int i;
        while ((i = isr.read()) != -1) {
            System.out.print((char) i);
        }
        isr.close();
    }

    /*
      -----------------------------序列化与反序列化------------------------------------
      定义：用来将对象进行存储，将对象利用字节方式存储进文件为序列化，将对象从文件中读取叫反序列化。
      ObjectInputStream和ObjectOutputStream
      构造方法：
      输出的：ObjectOutputStream(OutputStream out) 创建一个写入指定的OutputStream的ObjectOutputStream。参数是字节输出流
      输入的：ObjectInputStream(InputStream in) 创建从指定的InputStream读取的ObjectInputStream。
      特有方法：
      输出的：void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
      输入的：Object readObject() 从ObjectInputStream读取一个对象。
      使用步骤：
      1.先创建一个类及其对象a，并实现接口Serializable（不实现是会抛出异常的）
      2.再创建ObjectOutputStream对象，调用writeObject方法，参数是a（即第一步创建的对象），把对象写入文件中
      3.释放资源
      4.创建ObjectInputStream对象，调用readObject方法，将对象读取出来，除了io异常外还有一个关于class文件的异常，即需要有这个文件，读取的对象用类型为Object的变量接收。
      5.释放资源，打印对象。
     **/
    public static void serializeTest() throws IOException, ClassNotFoundException {
        A a = new A("好好学习");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\序列化测试.txt"));
        oos.writeObject(a);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\序列化测试.txt"));
        Object obj = ois.readObject();
        ois.close();
        System.out.println(obj);
    }

    static class A implements Serializable {
        private String str;

        public A(String str) {
            this.str = str;
        }

        //为了输出具体的数值
        @Override
        public String toString() {
            return "A{" +
                    "str='" + str + '\'' +
                    '}';
        }
    }

}
